{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deutsch–Jozsa Algorithm Tutorial (Part IV)\n",
    "\n",
    "The **Deutsch–Jozsa algorithm** is one of the most famous algorithms in quantum computing. The problem it solves has little practical value, but the algorithm itself is one of the earliest examples of a quantum algorithm that is exponentially faster than any possible deterministic algorithm for the same problem. It is also relatively simple to explain and illustrates several very important concepts (such as quantum oracles). As such, Deutsch–Jozsa algorithm is part of almost every introductory course on quantum computing.\n",
    "\n",
    "This tutorial consists of several notebooks:\n",
    "1. [Part I](./../DeutschJozsaAlgorithmTutorial_P1.ipynb) covers the problem statement and the classical algorithm for solving it.\n",
    "2. [Part II](./../DeutschJozsaAlgorithmTutorial_P2.ipynb) introduces single-qubit quantum oracles and the Deutsch algorithm for solving the problem for one-bit input functions.\n",
    "3. [Part III](./../DeutschJozsaAlgorithmTutorial_P3.ipynb) introduces multi-qubit quantum oracles and the Deutsch-Jozsa algorithm for solving the general case of the problem.\n",
    "4. Part IV shows how to run the algorithms in the cloud using Azure Quantum."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Part IV. Running algorithms on Azure Quantum\n",
    "\n",
    "Now that you're familiar with the algorithms covered in this tutorial, let's look into the final step of a quantum algorithm's implementation: running them on quantum hardware!\n",
    "\n",
    "This part of the tutorial doesn't have automatically graded exercises; instead, it's structured as a follow-along demo, so that by executing each cell in turn you can learn the steps of running jobs via the cloud and the nuances of how it is different from using a local simulator.\n",
    "\n",
    "## Define operations\n",
    "\n",
    "To start with, we'll define the operations that implement the oracles and the algorithms again; Q# notebooks can use operations defined in them or in .qs files in the same folder, but not in other notebooks."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "// Phase oracles we'll use to demonstrate Deutsch algorithm\n",
    "operation PhaseOracle_Zero (x : Qubit) : Unit {\n",
    "    // Do nothing...\n",
    "}\n",
    "\n",
    "operation PhaseOracle_X (x : Qubit) : Unit {\n",
    "    Z(x);\n",
    "}\n",
    "\n",
    "operation DeutschAlgorithm (oracle : (Qubit => Unit)) : Result {\n",
    "    use x = Qubit();\n",
    "    H(x);\n",
    "    oracle(x);\n",
    "    H(x);\n",
    "    return M(x);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that we've modified the signature of the operation that implements Deutsch algorithm to return `Result` instead of `Bool`; `Zero` indicates a constant function and `One` - a balanced function. We'll see why we needed this change once we get to running the algorithm on hardware!\n",
    "\n",
    "After this, we need to define the wrapper operation that will call the Deutsch algorithm with the right oracle parameter depending on a string parameter.\n",
    "Q# notebooks allow to pass parameters of different types to operations executed via magics, but they cannot pass callables as parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "operation DeutschAlgorithmWrapper (oracleFunction : String) : Result {\n",
    "    Message($\"Running Deutsch algorithm for f(x) = {oracleFunction}\");\n",
    "    if oracleFunction == \"0\" {\n",
    "        return DeutschAlgorithm(PhaseOracle_Zero);\n",
    "    } elif oracleFunction == \"x\" {\n",
    "        return DeutschAlgorithm(PhaseOracle_X);\n",
    "    } else {\n",
    "        fail $\"Unknown function f(x) = {oracleFunction}\";\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's run the algorithm on a simulator like we did before, to make sure we didn't introduce any unexpected behaviors (think of it as unit tests for your code!)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "%simulate DeutschAlgorithmWrapper oracleFunction=0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "%simulate DeutschAlgorithmWrapper oracleFunction=x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Connect to Azure Quantum\n",
    "\n",
    "You need to substitute `<subscriptionId>`, `<resourceGroupId>`, `<workspaceId>`, and `<location>` in the connection string below with the parameter values from your own quantum workspace.\n",
    "You can learn how to create an Azure Quantum workspace in [this Microsoft Learn module](https://docs.microsoft.com/learn/modules/get-started-azure-quantum/3-create-quantum-workspace).\n",
    "(Executing this cell will open a new tab with a login prompt; return to the notebook once you log in.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "azure_quantum"
    ]
   },
   "outputs": [],
   "source": [
    "%azure.connect \"/subscriptions/<subscriptionId>/resourceGroups/<resourceGroupId>/providers/Microsoft.Quantum/Workspaces/<workspaceId>\" location=\"<location>\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `%azure.connect` magic outputs the list of targets available in your subscription. \n",
    "If you added IonQ as the provider to your workspace, you'll get `ionq.simulator` (the classical simulator of a quantum device) and `ionq.qpu` (the actual quantum device).\n",
    "Let's submit a job for the simulator first; we'll need to choose the target using the `%azure.target` magic command."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true,
    "tags": [
     "azure_quantum"
    ]
   },
   "outputs": [],
   "source": [
    "%azure.target ionq.simulator"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, you can submit a job to the current target using the `%azure.submit` magic command which takes the same parameters as the `%simulate` magic command used earlier.\n",
    "\n",
    "When you run this cell, you'll get a job id in return. You'll use this id for looking up the job status, either via the `%azure.status` command or via Azure Portal, and for retrieving job results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false,
    "tags": [
     "azure_quantum"
    ]
   },
   "outputs": [],
   "source": [
    "%azure.submit DeutschAlgorithmWrapper oracleFunction=0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Running `%azure.status` without any arguments will fetch the status of the latest job executed in this notebook; if you need to look up an earlier job, you can use its id as a parameter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false,
    "tags": [
     "azure_quantum"
    ]
   },
   "outputs": [],
   "source": [
    "%azure.status"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Similarly, running `%azure.output` without any arguments will fetch the results of the latest job executed in this notebook; if you need to look up an earlier job, you can use its id as a parameter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": [
     "azure_quantum"
    ]
   },
   "outputs": [],
   "source": [
    "%azure.output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The format of job execution results differs from the results produced by the local simulator; instead of a single run result, you get a histogram of results of multiple job runs (\"shots\").\n",
    "In this case, though, the job we run produces a deterministic result, and in the absence of noise the histogram consists of a single entry.\n",
    "If you run the same job on quantum hardware without error correction, or on a noisy simulator, you'll get a distribution of multiple results, and will need to analyze the data to figure out the right answer.\n",
    "\n",
    "## Exercises\n",
    "\n",
    "Here are some things you can try as an exercise to practice running jobs in the cloud and interpreting the results:\n",
    "\n",
    "* Run the same code on quantum hardware: change the execution target to the QPU using `%azure.target ionq.qpu`, and repeat the same sequence of steps you used to run it on the simulator. Note that running the job on a simulator is free but running it on hardware is not! Make sure you're using [Azure Quantum Credits Program](https://docs.microsoft.com/azure/quantum/credits-faq), or feel free to skip this exercise.\n",
    "* Modify Deutsch-Jozsa algorithm from part III to run in the cloud (you'll need to adjust your implementation similarly to how the Deutsch algorithm implementation in this tutorial differs from the one in part II)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Q#",
   "language": "qsharp",
   "name": "iqsharp"
  },
  "language_info": {
   "file_extension": ".qs",
   "mimetype": "text/x-qsharp",
   "name": "qsharp",
   "version": "0.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
